package tech.yixiyun.framework.kuafu.db.sql;

import cn.hutool.core.util.ArrayUtil;
import tech.yixiyun.framework.kuafu.db.DbException;
import tech.yixiyun.framework.kuafu.db.DbKit;
import tech.yixiyun.framework.kuafu.db.datasource.DataSourceContext;
import tech.yixiyun.framework.kuafu.db.datasource.DataSourceDefinition;
import tech.yixiyun.framework.kuafu.db.datasource.DbType;
import tech.yixiyun.framework.kuafu.db.sql.handler.ColumnProcessor;
import tech.yixiyun.framework.kuafu.domain.BaseDomain;
import tech.yixiyun.framework.kuafu.kits.JSONKit;
import tech.yixiyun.framework.kuafu.kits.StringKit;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

/**
 * 用于拼接sql语句的类
 *
 * @author Yixiyun
 * @version 1.0
 * @date 2021-05-07 14:18
 */
public abstract class Sql implements Serializable{

    protected Sql(){}

    /**
     * 各种数据库类型的语句实现类
     */
    private static final Map<DbType, Class<? extends Sql>> dbSqlMapper = new HashMap<>();

    protected static void registerSql(DbType dbType, Class<? extends Sql> clazz) {
        synchronized (dbSqlMapper) {
            dbSqlMapper.put(dbType, clazz);
        }
    }
    /**
     * 获取某个数据库类型的sql执行器
     * @param dbType
     * @return
     */
    public static Class<? extends Sql> getSqlClass(DbType dbType) {
        Class<? extends Sql> clazz = dbSqlMapper.get(dbType);
        if (clazz == null) {
            throw new DbException("未注册"+dbType+"的SqlExecutor");
        }
        return clazz;
    }


    /**
     * 数据源
     */
    protected DataSourceDefinition dataSource;

    /**
     * 对应的Domain类
     */
    protected Class domainClass;

    /**
     * 对象实例，一般在根据一个实例生成insert语句时会用到。
     */
    protected BaseDomain instance;

    /**
     * 使用主数据源构造一个Sql实例
     * @return
     */
    public static Sql build() {
        return build(DataSourceContext.getMainSourceName(), (String) null, null);
    }
    /**
     * 使用主数据源构造一个updateSql实例
     * @return
     */
    public static Sql buildUpdate() {
        return build().update();
    }
    /**
     * 使用主数据源构造一个deleteSql实例
     * @return
     */
    public static Sql buildDelete() {
        return build().delete();
    }



    /**
     * 根据一个子语句作为表名构造一个新的语句，子语句的别名默认为this
     * @return
     */
    public static Sql build(Sql sql) {
        return build(sql.getDataSourceName(), (String) null, null).from("this", sql);
    }

    /**
     * 根据一个子语句作为表名构造一个新的语句，子语句的别名默认为this
     * @param sql
     * @param alias 子语句别名
     * @return
     */
    public static Sql build(Sql sql, String alias) {
        return build(sql.getDataSourceName(), (String) null, null).from(alias, sql);
    }

    /**
     * 使用主数据源构造一个Sql实例，并指定要操作的表名
     * @param tableName
     * @return
     */
    public static Sql build(String tableName) {
        return build(DataSourceContext.getMainSourceName(), tableName, null);
    }
    /**
     * 使用主数据源构造一个update Sql实例，并指定要操作的表名
     * @param tableName
     * @return
     */
    public static Sql buildUpdate(String tableName) {
        return build(tableName).update();
    }
    /**
     * 使用主数据源构造一个delete Sql实例，并指定要操作的表名
     * @param tableName
     * @return
     */
    public static Sql buildDelete(String tableName) {
        return build(tableName).delete();
    }
    /**
     * 构造一个Sql实例，并根据domain类自动识别数据源和表名
     * @param domainClass
     * @return
     */
    public static Sql build(Class<? extends BaseDomain> domainClass) {
        Sql sql = build(DbKit.getDataSource(domainClass), DbKit.tableName(domainClass), domainClass);
        return sql;
    }
    /**
     * 构造一个update Sql实例，并根据domain类自动识别数据源和表名
     * @param domainClass
     * @return
     */
    public static Sql buildUpdate(Class<? extends BaseDomain> domainClass) {
        return build(domainClass).update();
    }
    /**
     * 构造一个delete Sql实例，并根据domain类自动识别数据源和表名
     * @param domainClass
     * @return
     */
    public static Sql buildDelete(Class<? extends BaseDomain> domainClass) {
        return build(domainClass).delete();
    }
    /**
     * 构造一个Sql实例，并根据domain类自动识别数据源和表名
     * @param domainClass
     * @return
     */
    public static Sql build(Class<? extends BaseDomain> domainClass, Serializable... tableNameArgs) {
        Sql sql = build(DbKit.getDataSource(domainClass), DbKit.tableName(domainClass, tableNameArgs), domainClass);
        return sql;
    }
    /**
     * 构造一个update Sql实例，并根据domain类自动识别数据源和表名
     * @param domainClass
     * @return
     */
    public static Sql buildUpdate(Class<? extends BaseDomain> domainClass, Serializable... tableNameArgs) {
        return build(domainClass, tableNameArgs).update();
    }
    /**
     * 构造一个delete Sql实例，并根据domain类自动识别数据源和表名
     * @param domainClass
     * @return
     */
    public static Sql buildDelete(Class<? extends BaseDomain> domainClass, Serializable... tableNameArgs) {
        return build(domainClass, tableNameArgs).delete();
    }
    /**
     * 通过指定名字的数据源构造一个Sql实例，并根据domain类指定要操作的表名
     * @param domainClass
     * @return
     */
    public static Sql build(String dataSourceName, Class domainClass) {
        Sql sql = build(dataSourceName, DbKit.tableName(domainClass), domainClass);
        return sql;
    }
    /**
     * 通过指定名字的数据源构造一个update Sql实例，并根据domain类指定要操作的表名
     * @param domainClass
     * @return
     */
    public static Sql buildUpdate(String dataSourceName, Class domainClass) {
        return build(dataSourceName, domainClass).update();
    }
    /**
     * 通过指定名字的数据源构造一个delete Sql实例，并根据domain类指定要操作的表名
     * @param domainClass
     * @return
     */
    public static Sql buildDelete(String dataSourceName, Class domainClass) {
        return build(dataSourceName, domainClass).delete();
    }
    /**
     * 通过指定名字的数据源构造一个Sql实例，并根据domain类指定要操作的表名
     * @param domainClass
     * @return
     */
    public static Sql build(String dataSourceName, Class domainClass, Serializable... tableNameArgs) {
        Sql sql = build(dataSourceName, DbKit.tableName(domainClass, tableNameArgs), domainClass);
        return sql;
    }
    /**
     * 通过指定名字的数据源构造一个update Sql实例，并根据domain类指定要操作的表名
     * @param domainClass
     * @return
     */
    public static Sql buildUpdate(String dataSourceName, Class domainClass, Serializable... tableNameArgs) {
        return build(dataSourceName, domainClass, tableNameArgs).update();
    }
    /**
     * 通过指定名字的数据源构造一个delete Sql实例，并根据domain类指定要操作的表名
     * @param domainClass
     * @return
     */
    public static Sql buildDelete(String dataSourceName, Class domainClass, Serializable... tableNameArgs) {
        return build(dataSourceName, domainClass, tableNameArgs).delete();
    }
    /**
     * 通过指定名字的数据源构造一个Sql实例，同时指定要操作的表名。表名可以传个null进去，后面再通过调用实例的from方法指定。
     * @param dataSourceName
     * @param tableName 要操作的表名，可以传个null，后面再通过Sql的from设置tableName
     * @return
     */
    public static Sql build(String dataSourceName, String tableName) {
        return build(dataSourceName, tableName, null);
    }
    /**
     * 通过指定名字的数据源构造一个update Sql实例，同时指定要操作的表名。表名可以传个null进去，后面再通过调用实例的from方法指定。
     * @param dataSourceName
     * @param tableName 要操作的表名，可以传个null，后面再通过Sql的from设置tableName
     * @return
     */
    public static Sql buildUpdate(String dataSourceName, String tableName) {
        return build(dataSourceName, tableName).update();
    }
    /**
     * 通过指定名字的数据源构造一个delete Sql实例，同时指定要操作的表名。表名可以传个null进去，后面再通过调用实例的from方法指定。
     * @param dataSourceName
     * @param tableName 要操作的表名，可以传个null，后面再通过Sql的from设置tableName
     * @return
     */
    public static Sql buildDelete(String dataSourceName, String tableName) {
        return build(dataSourceName, tableName).delete();
    }

    /**
     * 通过指定数据源构造一个Sql实例
     * @param dataSourceName 数据源名称
     * @param domainClass 表对应的Domain类
     * @param tableName 操作的表名
     * @return
     */
    public static Sql build(String dataSourceName, String tableName, Class<? extends BaseDomain> domainClass) {
        DataSourceDefinition ds = DataSourceContext.getDataSource(dataSourceName);
        Class dbSqlClass = dbSqlMapper.get(ds.getDbType());
        if (dbSqlClass == null) {
            throw new DbException(ds.getDbType() + "类型数据库未注册对应的Sql实现类，请通过Sql.register方法注册");
        }

        try {
            Sql sql = (Sql) dbSqlClass.newInstance();
            if (StringKit.isNotBlank(tableName)) {
                sql.from(tableName);
            }
            sql.dataSource = ds;
            sql.domainClass =  domainClass;
            return sql;
        } catch (Exception e) {
            throw new DbException(e);
        }
    }
    /**
     * 通过指定数据源构造一个update Sql实例
     * @param dataSourceName 数据源名称
     * @param domainClass 表对应的Domain类
     * @param tableName 操作的表名
     * @return
     */
    public static Sql buildUpdate(String dataSourceName, String tableName, Class<? extends BaseDomain> domainClass) {
        return build(dataSourceName, tableName, domainClass).update();
    }
    /**
     * 通过指定数据源构造一个update Sql实例
     * @param dataSourceName 数据源名称
     * @param domainClass 表对应的Domain类
     * @param tableName 操作的表名
     * @return
     */
    public static Sql buildDelete(String dataSourceName, String tableName, Class<? extends BaseDomain> domainClass) {
        return build(dataSourceName, tableName, domainClass).delete();
    }

        /**
         * 条件之间连接关系 and
         */
    public static final String AND = "and";

    /**
     * 条件之间连接关系 or
     */
    public static final String OR = "or";

    /**
     * 左括号
     */
    public static final String BRACKET_LEFT = "(";
    /**
     * 右括号
     */
    public static final String BRACKET_RIGHT = ")";

    /**
     * 下次拼接时条件之间的关系
     */
    protected String link = "and";

    /**
     * 将下次的关系改成or
     * @return
     */
    public Sql or() {
        setLink(OR);
        return this;
    }
    /**
     * 获取下一次条件关系，每次获取完后，会重置link为and，直到手动调用or或者setLink
     * @return
     */
    protected String getLink() {
        String link = this.link;
        setLink(AND);
        return link;
    }

    /**
     * 设置下一次的拼接关系
     * @param link
     */
    protected void setLink(String link) {
        this.link = link;
    }

    /**
     * where中条件拼接(，只能在where中用
     * @return
     */
    public abstract Sql bracketLeft() ;

    /**
     * where中条件拼接)，只能在where中用
     * @return
     */
    public abstract Sql bracketRight();





    /**
     * 操作类型，默认是查询
     */
    protected SqlType type= SqlType.QUERY;

    /**
     * 获取语句操作类型
     * @return
     */
    public SqlType getType() {
        return this.type;
    }

    /**
     * 获取建表语句
     * @return
     */
    public Sql create() {
        this.type = SqlType.CREATE;
        return this;
    }
    /**
     * 获取修改表语句
     * @return
     */
    public Sql alter() {
        this.type = SqlType.ALTER;
        return this;
    }
    /**
     * 根据一个实例构建一个插入语句
     * @param instance
     * @return
     */
    public Sql insert(BaseDomain instance) {
        this.type = SqlType.INSERT;
        this.instance = instance;
        return this;
    }


    public Sql save(BaseDomain instance) {
        this.type = SqlType.SAVE;
        this.instance = instance;
        return this;
    }

    /**
     * 表明语句是一个查询语句，一般会在语句前端自动拼上select *
     * @return
     */
    public Sql select() {
        this.type = SqlType.QUERY;
        return this;
    }

    /**
     * 进行select查询，并将结果加锁，请注意条件中一定要有索引列，否则会锁住整个表。
     * 请自行查阅 select for update 相关注意事项
     * @return
     */
    public Sql selectForUpdate() {
        this.type = SqlType.QUERY;
        return this.forUpdate();
    }

    /**
     * 将select的结果加锁，该方法只能用于select语句，并且条件中一定要有索引列，否则会锁住整个表。
     * 请自行查阅 select for update 相关注意事项
     * @return
     */
    public abstract Sql forUpdate();

    /**
     * 执行select查询，并指定返回列。直接传入 列名部分 就行，不用写select。例如直接传入 id,name,age 即可
     * @param columns
     * @return
     */
    public abstract Sql select(CharSequence columns) ;

    /**
     * 在select中指定值，比如 select 'a',1 from dual。
     * @param values
     * @return
     */
    public abstract Sql select(Serializable[] values);

    /**
     * 执行select查询，并指定返回列。直接传入 列名部分 就行，不用写select。例如直接传入 id,name,age 即可
     * 该方法会将结果加锁，请注意条件中一定要有索引列，否则会锁住整个表。
     * @param columns
     * @return
     */
    public Sql selectForUpdate(CharSequence columns) {
        this.select(columns);
        return this.forUpdate();
    }

    /**
     * 分析当前Sql指定的domain类，将columns中的字段排除，其他字段拼成select 子语句。
     * 所以调用该方法前，需要保证已指定DomainClass。
     * @param columns
     * @return
     */
    public abstract Sql selectWithout(CharSequence columns);

    /**
     * 分析当前Sql指定的domain类，将columns中的字段排除，其他字段拼成select 子语句。
     * 所以调用该方法前，需要保证已指定DomainClass
     * 该方法会将结果加锁，请注意条件中一定要有索引列，否则会锁住整个表。
     * @param columns
     * @return
     */
    public  Sql selectWithoutForUpdate(CharSequence columns){
        this.selectWithout(columns);
        return this.forUpdate();
    }


    /**
     * 根据class找到所有字段，然后排除掉指定的字段，其他字段拼接成select子句。<br/>
     * <b>注意：这个方法不能和select同时使用，也不会根据domainClass设置tableName</b>
     *
     * @param columns
     * @param domainClass
     * @return
     */
    public abstract Sql selectWithout(CharSequence columns, Class domainClass);


    /**
     * 根据class找到所有字段，然后排除掉指定的字段，其他字段拼接成select子句。<br/>
     * <b>注意：这个方法不能和select同时使用，也不会根据domainClass设置tableName</b>
     * 所以调用该方法前，需要保证已指定DomainClass.
     * 该方法会将结果加锁，请注意条件中一定要有索引列，否则会锁住整个表。
     * @param columns
     * @return
     */
    public  Sql selectWithoutForUpdate(CharSequence columns, Class domainClass){
        this.selectWithout(columns, domainClass);
        return this.forUpdate();
    }

    /**
     * 指定语句为update语句
     * @return
     */
    public Sql update() {
        this.type = SqlType.UPDATE;
        return this;
    }
    /**
     * 指定语句是一个删除语句
     * @return
     */
    public Sql delete() {
        this.type = SqlType.DELETE;
        return this;
    }

    /**
     * 根据指定的列和值构建一个插入语句
     * @param cols
     * @param values
     * @return
     */
    public Sql insert(String[] cols, Serializable[] values) {

        return insert(cols, new Object[][]{values});
    }

    public Sql insert(String[] cols, Serializable[][] values) {
        this.type = SqlType.INSERT;
        this.setInsertColAndValues(cols, values);
        return this;
    }


    /**
     * 根据指定的列和查询语句，构建一个插入语句。
     * @param cols
     * @param querySql
     * @return
     */
    public Sql insert(String[] cols, Sql querySql) {
        this.type = SqlType.INSERT;
        this.setInsertColAndValues(cols, querySql);
        return this;
    }
    /**
     * 为insert语句设置 字段和值
     * @param cols
     * @param values
     * @return
     */
    public abstract Sql setInsertColAndValues(String[] cols, Serializable[][] values);
    /**
     * 为insert语句设置 字段和值，但值来源于一个查询语句
     * @param cols
     * @param querySql
     * @return
     */
    public abstract Sql setInsertColAndValues(String[] cols, Sql querySql);


    /**
     * 拼接 from 子语句，根据 domainClass 自动拼接表名，注意这里domainClass只能是单表domain
     * @param domainClass
     * @return
     */
    public abstract Sql from(Class domainClass) ;

    /**
     * 拼接from 子语句，会根据domaiclass自动识别表名，并同时指定一个别名
     * @param aliasName
     * @param domainClass
     * @return
     */
    public abstract Sql from(String aliasName, Class domainClass) ;

    /**
     * 拼接 from 子语句，根据 domainClass 上的 splitRule规则生成表名，注意这里domainClass必须是分表Domain
     * @param domainClass
     * @param tableNameArgs
     * @return
     */
    public abstract Sql from(Class domainClass, Serializable... tableNameArgs);
    /**
     * 拼接 from 子语句，根据 domainClass 上的 splitRule规则生成表名，并指定一个别名。注意这里domainClass必须是分表Domain
     * @param domainClass
     * @param tableNameArgs
     * @return
     */
    public abstract Sql from(String aliasName, Class domainClass, Serializable... tableNameArgs);

    /**
     * 拼接from子语句
     * @param tableName
     * @return
     */
    public abstract Sql from(String tableName);

    /**
     * 拼接from子语句，并指定一个别名 ,例如 from tbuser a 或者 from (select * from tbuser) a
     * @param aliasName
     * @param tableName
     * @return
     */
    public abstract Sql from(String aliasName, String tableName);
    /**
     * 拼接from子语句，并指定一个别名 ,例如 from (select * from tbuser) a
     * @param aliasName
     * @param table
     * @return
     */
    public abstract Sql from(String aliasName, Sql table);

    /**
     * 拼接from子语句，适用于多个子语句，例如 select * from xxx a, yyyy b, (select * from zzz) c。
     * 所以此处args只支持CharSequence类型和Sql类型参数，需要按照{子语句、别名、子语句、别名} 来构造 args参数
     * @param args
     * @return
     */
    public abstract Sql fromMultiTable(Serializable... args);


    /**
     * 拼接where 条件子句，注意args不要只传一个null，加入你要拼接一个 xxx is null的条件，那condition直接传入"xxx is null"，
     * 千万不要condition传入 "xxx is ?" ， 然后args 传入 null，这种写法是错误的，会报异常。
     * @param condition
     * @param args
     * @return
     */
    public abstract Sql where(CharSequence condition, Serializable... args);


    /**
     * 拼接 a=b and c = d and ...的语句。传入的参数必须按照 列名,值，列名，值传入
     * @param columnAndValues 列名必须是字符串
     * @return
     */
    public Sql eq(Serializable... columnAndValues) {
        if (columnAndValues == null || columnAndValues.length == 0) {
            return this;
        }
        if (columnAndValues.length % 2 != 0) {
            throw new SqlException("传入的参数数量不是双数");
        }
        for (int i = 0; i < columnAndValues.length; i+=2) {
            this.eq((String)columnAndValues[i], columnAndValues[i+1]);
        }
        return this;
    }

    /**
     * where条件语句拼接 column = value
     * @param column
     * @param value
     * @return
     */
    public abstract Sql eq(String column, Serializable value);

    /**
     * where条件语句拼接 column = value，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public Sql eqIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return eq(column, value);
        }
        return this;
    }

    /**
     * where条件语句拼接 column != value
     * @param column
     * @param value
     * @return
     */
    public abstract Sql ueq(String column, Serializable value);

    /**
     * where条件语句拼接 column != value，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public  Sql ueqIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return ueq(column, value);
        }
        return this;
    }


    /**
     * where条件拼接 column between x and y
     * @param column
     * @param startValue
     * @param endValue
     * @return
     */
    public abstract Sql between(String column, Serializable startValue, Serializable endValue);

    /**
     * where条件拼接 column not between x and y
     * @param column
     * @param startValue
     * @param endValue
     * @return
     */
    public abstract Sql notBetween(String column, Serializable startValue, Serializable endValue);

    /**
     * where条件拼接 column > value
     * @param column
     * @param value
     * @return
     */
    public abstract Sql gt(String column, Serializable value);

    /**
     * where条件拼接 column > value，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public Sql gtIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return gt(column, value);
        }
        return this;
    }
    /**
     * where条件拼接 column >= value
     * @param column
     * @param value
     * @return
     */
    public abstract Sql gte(String column, Serializable value);
    /**
     * where条件拼接 column >= value，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public  Sql gteIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return gte(column, value);
        }
        return this;
    }


    /**
     * where条件拼接 column < value
     * @param column
     * @param value
     * @return
     */
    public abstract Sql lt(String column, Serializable value);
    /**
     * where条件拼接 column < value，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public  Sql ltIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return lt(column, value);
        }
        return this;
    }

    /**
     * where条件拼接 column <= value
     * @param column
     * @param value
     * @return
     */
    public abstract Sql lte(String column, Serializable value);
    /**
     * where条件拼接 column <= value，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public Sql lteIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return lte(column, value);
        }
        return this;
    }

    /**
     * where条件拼接 column is null
     * @param column
     * @return
     */
    public abstract Sql isNull(String column);
    /**
     * where条件拼接 column is not null
     * @param column
     * @return
     */
    public abstract Sql isNotNull(String column);

    /**
     * where条件拼接 column like %value%
     * @param column
     * @param value
     * @return
     */
    public abstract Sql like(String column, Serializable value);
    /**
     * where条件拼接 column like %value%，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public Sql likeIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return like(column, value);
        }
        return this;
    }

    /**
     * where条件拼接 column like value%
     * @param column
     * @param value
     * @return
     */
    public abstract Sql likeLeft(String column, Serializable value);

    /**
     * where条件拼接 column like value%，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public Sql likeLeftIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return likeLeft(column, value);
        }
        return this;
    }
    /**
     * where条件拼接 column like %value
     * @param column
     * @param value
     * @return
     */
    public abstract Sql likeRight(String column, Serializable value);
    /**
     * where条件拼接 column like %value，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public Sql likeRightIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return likeRight(column, value);
        }
        return this;
    }

    /**
     * where条件拼接 column not like %value%
     * @param column
     * @param value
     * @return
     */
    public abstract Sql notLike(String column, Serializable value);
    /**
     * where条件拼接 column not like %value%，值有效才会去拼接
     * @param column
     * @param value
     * @return
     */
    public Sql notLikeIfOk(String column, Serializable value) {
        if (DbKit.isOk(value)) {
            return notLike(column, value);
        }
        return this;
    }

    /**
     * where条件拼接 column in (value, value, value)
     * @param column
     * @param values
     * @return
     */
    public abstract Sql in(String column, Serializable... values);

    /**
     * 如果传入的数组有效（不为null且length > 0），才去拼接in语句
     * @param column
     * @param values
     * @return
     */
    public Sql inIfOk(String column, Serializable... values) {
        if (values != null && values.length > 0) {
            this.in(column, values);
        }
        return this;
    }
    /**
     * where条件拼接 column in (子语句)
     * @param column
     * @param sql
     * @return
     */
    public abstract Sql in(String column, Sql sql);
    /**
     * where条件拼接 column in (子语句)
     * @param column
     * @param sql
     * @return
     */
    public abstract Sql in(String column, String sql);
    /**
     * where条件拼接 column not in (value, value, value)
     * @param column
     * @param values
     * @return
     */
    public abstract Sql notIn(String column, Serializable... values);

    /**
     * 如果传入的数组有效（不为null且length > 0），才去拼接not in语句
     * @param column
     * @param values
     * @return
     */
    public Sql notInIfOk(String column,  Serializable... values) {
        if (values != null && values.length > 0) {
            notIn(column, values);
        }
        return this;
    }

    /**
     * where条件拼接 column not in (子语句)
     * @param column
     * @param sql
     * @return
     */
    public abstract Sql notIn(String column, Sql sql);
    /**
     * where条件拼接 column not in (子语句)
     * @param column
     * @param sql
     * @return
     */
    public abstract Sql notIn(String column, String sql);

    /**
     * where条件中拼接 exist
     * @param sql
     * @return
     */
    public abstract Sql exists(Sql sql);

    /**
     * where条件中拼接 not exist
     * @param sql
     * @return
     */
    public abstract Sql notExists(Sql sql);

    /**
     * 拼接 group by
     * @param groupColumns
     * @return
     */
    public abstract Sql group(String groupColumns);
    /**
     * 拼接 having, having后面继续使用提供的条件拼接方法
     * @return
     */
    public abstract Sql having(String having);

    /**
     * 拼接order by ,数组必须是 列名、顺序、列名、顺序... 这么一个结构的数组。
     * @param orders
     * @return
     */
    public abstract Sql order(String... orders);

    /**
     * 倒序排列
     * @param column
     * @return
     */
    public abstract Sql orderDesc(String column);
    /**
     * 升序排列
     * @param column
     * @return
     */
    public abstract Sql orderAsc(String column);


    /**
     * 拼接分页语句
     * @param page
     * @param pageSize
     * @return
     */
    public abstract Sql page(int page, int pageSize);

    /**
     * 拼接分页语句
     * @param pageSize
     * @return
     */
    public abstract Sql page(int pageSize);
    /**
     * 拼接leftjoin子语句
     * @param joinTable
     * @param aliasName
     * @param on
     * @return
     */
    public abstract Sql leftJoin(String joinTable, String aliasName, String on);
    /**
     * 拼接leftjoin子语句
     * @param joinTable
     * @param aliasName
     * @param on
     * @return
     */
    public abstract Sql leftJoin(Sql joinTable, String aliasName, String on);

    /**
     * 拼接union子语句 会自动去重
     * @param unionTable
     * @return
     */
    public abstract Sql union(Sql unionTable);
    /**
     * 拼接union子语句 会自动去重
     * @param unionTable
     * @return
     */
    public abstract Sql union(String unionTable);
    /**
     * 拼接union all子语句
     * @param unionTable
     * @return
     */
    public abstract Sql unionAll(Sql unionTable);
    /**
     * 拼接union all子语句
     * @param unionTable
     * @return
     */
    public abstract Sql unionAll(String unionTable);


    /**
     * update语句 set部分，数组中的数据必须是 column, value, column, value 成对输入
     * @param colAndValues
     * @return
     */
    public  Sql set(Serializable... colAndValues) {
        return set(true, colAndValues);
    }
    /**
     * update语句 set部分，数组中的数据必须是 column, value, column, value 成对输入
     * @param xss 是否需要对参数做xss处理
     * @param colAndValues
     * @return
     */
    public abstract Sql set(boolean xss, Serializable... colAndValues);

    /**
     * 获取PreparedStatement语句
     * @return
     */
    public String getStatement() {
        switch (this.type) {
            case QUERY:
                return getSelectStatement();
            case INSERT:
                return getInsertStatement();
            case UPDATE:
                return getUpdateStatement();
            case SAVE:
                return getSaveStatement();
            case DELETE:
                return getDeleteStatement();
            case CREATE:
                return getCreateTableStatement();
            case ALTER:
                return getAlterTableStatement();
            default:
                throw new SqlException("未指定语句类型或类型不支持");
        }
    }


    /**
     * 获取插入语句
     * @return
     */
    public abstract String getInsertStatement();

    /**
     * 获取插入或更新语句，根据主键判断已存在记录就更新，否则就插入
     * @return
     */
    public abstract String getSaveStatement();

    /**
     * 获取查询语句
     * @return
     */
    public abstract String getSelectStatement();

    /**
     * 获取更新语句
     * @return
     */
    public abstract String getUpdateStatement();

    /**
     * 获取删除语句
     * @return
     */
    public abstract String getDeleteStatement();


    /**
     * 获取建表语句
     * @return
     */
    public abstract String getCreateTableStatement();
    /**
     * 获取修改表结构的语句，修改表结构是一个非常危险的操作，尤其对于生产环境的数据。
     * 所以这个操作为了保证安全性，只将原表没有的字段加上，没有的索引加上，
     * 已存在的字段和索引都会保持原样不动。所以只建议在开发环境下使用该方法，生产环境手动升级表结构。
     * @return
     */
    public abstract String getAlterTableStatement();
    /**
     * 获取占位符对应的值
     * @return
     */
    public abstract Serializable[] getArgs();

    /**
     * 获取表名
     * @return
     */
    public abstract CharSequence getTableName();

    public DataSourceDefinition getDataSource() {
        return this.dataSource;
    }

    public String getDataSourceName() {
        return this.dataSource.getName();
    }

    public DbType getDbType() {
        return this.dataSource.getDbType();
    }

    public Class getDomainClass() {
        return domainClass;
    }

    public Sql setDomainClass(Class domainClass) {
        this.domainClass = domainClass;
        return this;
    }

    /**
     * 如果给定的值有效，继续对它进行拼接，否则直接返回。<br/>
     * 示例：sql.ifOk(keywords, (v, s) -> s.bracketLeft().like("name", v).or().like("remark", v).bracketRight())
     * @param value
     * @param function 接收两个参数，一个是给定的值，另一个是当前操作的sql实例
     * @param <T>
     * @return
     */
    public <T> Sql ifOk(T value, BiFunction<T, Sql, Sql> function) {
        if (DbKit.isOk(value)) {
            return function.apply(value, this);
        }
        return this;
    }






    /**
     * 对传入的arg做处理
     * @param arg
     * @return
     */
    public static Serializable processArgValue(Serializable arg) {
        if (arg == null) {
            return null;
        }
        Class clazz = arg.getClass();

        if (ColumnProcessor.isCommonColumnClass(clazz) || clazz == Exp.class) {
            return arg;
        }

        if (clazz.isEnum()) {
            return arg.toString();
        }

        if (clazz.isArray() ) {
            //byte[] 原样返回
            if (clazz.getComponentType() == byte.class) return arg;
            //基本类型[] 拼接成字符串
            if (ColumnProcessor.isCommonColumnClass(clazz.getComponentType())) {
                return ArrayUtil.join(arg, ",");
            }
        }
        //其他的都转成json字符串吧
        return JSONKit.toJson(arg);

    }

}
